Creating Custom Table View Cells and Collection View Cells
Course: iOS App Development
Topic: Creating Custom Table View Cells and Collection View Cells
Course Link: iOS Training at JustAcademy
Demo Link: Register for a Course Demo
Website: JustAcademy Official Website
1. Introduction
Table views and collection views are important UIKit components used to display lists and collections of data in iOS applications. A custom cell allows developers to design each item according to the application's requirements instead of relying only on standard text-and-image layouts.
A custom table view cell can display a profile picture, title, subtitle, date, status badge, and action button in a single row. A custom collection view cell can display product cards, photo thumbnails, category tiles, or dashboard items arranged in a grid or horizontal scrolling layout.
Custom cells are commonly used in social media applications, shopping apps, messaging applications, news feeds, contact lists, and photo galleries. Understanding how to create, configure, reuse, and update cells is an essential iOS development skill.
Learning Objectives
- Understand UITableView, UICollectionView, and their cell classes.
- Learn how to create custom cells using Swift and UIKit.
- Design cells using Storyboards, XIB files, or programmatic Auto Layout.
- Register and dequeue reusable cells correctly.
- Display model data inside custom cells.
- Handle cell selection and button actions.
- Understand cell reuse, dynamic sizing, and performance optimization.
- Build a practical list and grid interface.
2. What Is a Table View?
A table view is a UIKit component that displays data in a vertically scrolling list of rows. It is represented by the UITableView class, and each visible row is represented by a UITableViewCell.
Table views are suitable when users need to browse many items in a structured list. A row can contain multiple labels, images, buttons, switches, or other interface elements.
Common Uses of UITableView
- Contact lists and call history.
- Messages and notification feeds.
- Settings screens.
- News headlines and article lists.
- Expense histories and transaction lists.
- Task lists and saved notes.
Important UITableView Components
| Component |
Purpose |
| UITableView |
Displays and manages the scrolling list. |
| UITableViewCell |
Displays an individual row. |
| UITableViewDataSource |
Provides the number of sections, rows, and cells. |
| UITableViewDelegate |
Handles selection, row behavior, and related interactions. |
| IndexPath |
Identifies a row within a section. |
| Reusable cell identifier |
Identifies the cell type used when dequeuing a reusable cell. |
3. What Is a Collection View?
A collection view is a UIKit component used to display items in flexible layouts. It is represented by the UICollectionView class, and individual items are displayed using UICollectionViewCell.
Unlike a traditional table view, a collection view can arrange items in grids, horizontal carousels, vertically scrolling layouts, and more advanced custom arrangements.
Common Uses of UICollectionView
- Photo galleries and image grids.
- Product cards in shopping applications.
- Category tiles and dashboard shortcuts.
- Horizontal lists of recommended content.
- Music album and video thumbnails.
- Calendar layouts and interactive visual collections.
Important UICollectionView Components
| Component |
Purpose |
| UICollectionView |
Displays and manages a collection of items. |
| UICollectionViewCell |
Displays an individual collection item. |
| UICollectionViewDataSource |
Provides sections, item counts, and cells. |
| UICollectionViewDelegate |
Handles item selection and interactions. |
| UICollectionViewLayout |
Determines the position and size of cells and supplementary views. |
| UICollectionViewFlowLayout |
Arranges items in rows or columns with configurable spacing. |
4. Difference Between Table View Cells and Collection View Cells
| Feature |
Table View Cell |
Collection View Cell |
| Cell class |
UITableViewCell |
UICollectionViewCell |
| Main purpose |
Structured rows in a list |
Flexible item layouts |
| Typical layout |
Vertical rows |
Grid, horizontal list, or custom layout |
| Layout management |
Managed primarily by UITableView |
Controlled by a collection view layout object |
| Common example |
Messages or contacts |
Photos or product cards |
| Reusable cells |
Yes |
Yes |
| Supplementary views |
Headers and footers |
Headers, footers, and other supplementary views |
Both components use reusable cells to reduce memory usage and improve scrolling performance. The best choice depends on how the information should be presented and how much layout flexibility the interface requires.
5. Understanding Custom Cells
A custom cell is a cell whose interface and behavior have been tailored to display a particular type of data. Rather than placing all interface logic in the view controller, a custom cell class can manage its own labels, images, buttons, and layout.
Example: Custom Contact Cell
A contact list cell might contain:
- A circular profile image.
- A name label.
- A phone number label.
- A favorite icon.
- A call or message action button.
Example: Custom Product Cell
A product collection cell might contain:
- A product image.
- A product title.
- A price label.
- A rating indicator.
- An add-to-cart button.
Custom cells make complex layouts reusable. The same cell design can be configured with different model objects as users scroll through the list or grid.
6. Methods for Creating Custom Cells
UIKit supports several ways to create custom table view and collection view cells.
| Method |
Description |
Best Suited For |
| Storyboard |
Design the cell visually inside a storyboard. |
Projects that use Interface Builder. |
| XIB file |
Create a reusable cell layout in a separate interface file. |
Cells shared across multiple screens. |
| Programmatic UIKit |
Create views and constraints using Swift code. |
Code-driven interfaces and reusable components. |
| UIHostingConfiguration |
Use SwiftUI content inside a UIKit table or collection cell. |
UIKit apps that benefit from SwiftUI-based cell layouts on supported iOS versions. |
For learning purposes, programmatic UIKit is useful because it demonstrates how views, constraints, cell registration, and data configuration work together.
7. Creating a Custom UITableViewCell Class
To create a custom table view cell programmatically, subclass UITableViewCell. Add the interface elements to the cell's contentView and configure their layout with Auto Layout constraints.
Step-by-Step Process
- Open the iOS project in Xcode.
- Create a new Swift file named
ContactTableViewCell.swift.
- Import UIKit.
- Create a class that inherits from
UITableViewCell.
- Add labels, image views, or buttons.
- Add the views to the cell's content view.
- Define Auto Layout constraints.
- Create a configuration method that accepts the required data.
Example: ContactTableViewCell.swift
import UIKit
final class ContactTableViewCell: UITableViewCell {
static let reuseIdentifier = "ContactTableViewCell"
private let nameLabel = UILabel()
private let phoneLabel = UILabel()
private let avatarImageView = UIImageView()
override init(
style: UITableViewCell.CellStyle,
reuseIdentifier: String?
) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
}
private func setupViews() {
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
nameLabel.translatesAutoresizingMaskIntoConstraints = false
phoneLabel.translatesAutoresizingMaskIntoConstraints = false
avatarImageView.image = UIImage(systemName: "person.circle.fill")
avatarImageView.contentMode = .scaleAspectFill
avatarImageView.tintColor = .systemGray
avatarImageView.clipsToBounds = true
avatarImageView.layer.cornerRadius = 24
nameLabel.font = .preferredFont(forTextStyle: .headline)
nameLabel.adjustsFontForContentSizeCategory = true
phoneLabel.font = .preferredFont(forTextStyle: .subheadline)
phoneLabel.textColor = .secondaryLabel
phoneLabel.adjustsFontForContentSizeCategory = true
let textStack = UIStackView(
arrangedSubviews: [nameLabel, phoneLabel]
)
textStack.translatesAutoresizingMaskIntoConstraints = false
textStack.axis = .vertical
textStack.spacing = 4
contentView.addSubview(avatarImageView)
contentView.addSubview(textStack)
NSLayoutConstraint.activate([
avatarImageView.leadingAnchor.constraint(
equalTo: contentView.layoutMarginsGuide.leadingAnchor
),
avatarImageView.centerYAnchor.constraint(
equalTo: contentView.centerYAnchor
),
avatarImageView.widthAnchor.constraint(equalToConstant: 48),
avatarImageView.heightAnchor.constraint(equalToConstant: 48),
textStack.leadingAnchor.constraint(
equalTo: avatarImageView.trailingAnchor,
constant: 12
),
textStack.trailingAnchor.constraint(
equalTo: contentView.layoutMarginsGuide.trailingAnchor
),
textStack.topAnchor.constraint(
equalTo: contentView.topAnchor,
constant: 12
),
textStack.bottomAnchor.constraint(
equalTo: contentView.bottomAnchor,
constant: -12
)
])
}
func configure(name: String, phone: String) {
nameLabel.text = name
phoneLabel.text = phone
}
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = nil
phoneLabel.text = nil
avatarImageView.image = UIImage(systemName: "person.circle.fill")
}
}
This cell contains an avatar, name, and phone number. The configure method separates data assignment from layout creation, while prepareForReuse() resets reusable content before the cell is configured for another contact.
8. Creating a Contact Model
Models represent the information shown by the interface. Using a model keeps data organized and avoids passing unrelated values throughout the application.
import Foundation
struct Contact {
let name: String
let phone: String
}
In a larger application, a contact model may also include an identifier, email address, avatar URL, and additional properties. Stable identifiers are especially useful when the list supports editing, sorting, or animated updates.
9. Registering and Displaying Custom Table View Cells
When a custom cell is created in code, register its class with the table view. The data source can then dequeue a reusable instance when a row needs to be displayed.
Example: ContactsViewController.swift
import UIKit
final class ContactsViewController: UIViewController,
UITableViewDataSource,
UITableViewDelegate {
private let tableView = UITableView()
private let contacts = [
Contact(name: "Aarav Sharma", phone: "9876543210"),
Contact(name: "Meera Patel", phone: "9123456780"),
Contact(name: "Rohan Verma", phone: "9988776655")
]
override func viewDidLoad() {
super.viewDidLoad()
title = "Contacts"
view.backgroundColor = .systemBackground
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.register(
ContactTableViewCell.self,
forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier
)
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.topAnchor
),
tableView.leadingAnchor.constraint(
equalTo: view.leadingAnchor
),
tableView.trailingAnchor.constraint(
equalTo: view.trailingAnchor
),
tableView.bottomAnchor.constraint(
equalTo: view.bottomAnchor
)
])
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 72
}
func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
contacts.count
}
func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: ContactTableViewCell.reuseIdentifier,
for: indexPath
) as! ContactTableViewCell
let contact = contacts[indexPath.row]
cell.configure(name: contact.name, phone: contact.phone)
return cell
}
func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
tableView.deselectRow(at: indexPath, animated: true)
print("Selected: \(contacts[indexPath.row].name)")
}
}
The table view asks its data source for the number of rows and the cell for each visible row. The delegate handles selection and other user interactions. In a production application, consider a typed cell registration or another approach that avoids forced type casts where appropriate.
10. Understanding Cell Reuse in UITableView
Table views usually display more rows than can fit on the screen. Creating a brand-new cell for every row would consume unnecessary memory. Instead, UIKit reuses cells that have scrolled offscreen.
How Reuse Works
- The table view requests a cell for a visible row.
- The data source dequeues a reusable cell using its identifier.
- The cell is configured with the row's current data.
- When the row scrolls offscreen, the cell can enter the reuse pool.
- The same cell instance may later display a different contact or record.
Always configure all data-dependent views whenever a cell is reused. If an optional image, subtitle, or badge is not reset, old content can appear in a different row.
Common Reuse Problems
- Old text appearing in a different row.
- An image from a previous item remaining visible.
- A selected or favorite state appearing on the wrong record.
- Repeated event handlers or duplicate actions.
- Incorrect asynchronous image updates.
Use a dedicated configuration method and reset transient content in prepareForReuse(). For asynchronous image loading, verify that the image still belongs to the cell's current model before applying it.
11. Creating a Custom UICollectionViewCell Class
A custom collection view cell is created by subclassing UICollectionViewCell. Its layout is usually built inside the cell's contentView, with constraints that describe the item's size and arrangement.
Example: ProductCollectionViewCell.swift
import UIKit
final class ProductCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = "ProductCollectionViewCell"
private let imageView = UIImageView()
private let titleLabel = UILabel()
private let priceLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
}
private func setupViews() {
contentView.backgroundColor = .secondarySystemBackground
contentView.layer.cornerRadius = 12
contentView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
priceLabel.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(systemName: "shippingbox")
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .secondaryLabel
titleLabel.font = .preferredFont(forTextStyle: .headline)
titleLabel.numberOfLines = 2
titleLabel.adjustsFontForContentSizeCategory = true
priceLabel.font = .preferredFont(forTextStyle: .subheadline)
priceLabel.textColor = .secondaryLabel
priceLabel.adjustsFontForContentSizeCategory = true
let textStack = UIStackView(
arrangedSubviews: [titleLabel, priceLabel]
)
textStack.translatesAutoresizingMaskIntoConstraints = false
textStack.axis = .vertical
textStack.spacing = 6
contentView.addSubview(imageView)
contentView.addSubview(textStack)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(
equalTo: contentView.topAnchor,
constant: 12
),
imageView.leadingAnchor.constraint(
equalTo: contentView.leadingAnchor,
constant: 12
),
imageView.trailingAnchor.constraint(
equalTo: contentView.trailingAnchor,
constant: -12
),
imageView.heightAnchor.constraint(equalToConstant: 100),
textStack.topAnchor.constraint(
equalTo: imageView.bottomAnchor,
constant: 8
),
textStack.leadingAnchor.constraint(
equalTo: contentView.leadingAnchor,
constant: 12
),
textStack.trailingAnchor.constraint(
equalTo: contentView.trailingAnchor,
constant: -12
),
textStack.bottomAnchor.constraint(
lessThanOrEqualTo: contentView.bottomAnchor,
constant: -12
)
])
}
func configure(title: String, price: String) {
titleLabel.text = title
priceLabel.text = price
}
override func prepareForReuse() {
super.prepareForReuse()
titleLabel.text = nil
priceLabel.text = nil
imageView.image = UIImage(systemName: "shippingbox")
}
}
The example creates a product card with an image, title, and price. A collection view layout determines the size and position of each card within the overall grid or scrolling layout.
12. Creating a Product Model
A product model holds the values displayed in the collection view.
import Foundation
struct Product {
let title: String
let price: Decimal
}
Using Decimal for price values helps avoid some binary floating-point rounding issues. In a real shopping application, format the price using a locale-aware currency formatter and define the currency explicitly.
13. Setting Up a Collection View
To display a custom collection cell, configure a UICollectionView, register the cell class, and implement the collection view data source methods. A flow layout can be used for a simple grid.
Example: ProductGridViewController.swift
import UIKit
final class ProductGridViewController: UIViewController,
UICollectionViewDataSource,
UICollectionViewDelegate {
private var collectionView: UICollectionView!
private let products = [
Product(title: "Wireless Headphones", price: 2499),
Product(title: "Smart Watch", price: 3999),
Product(title: "Bluetooth Speaker", price: 1799),
Product(title: "Phone Stand", price: 499),
Product(title: "USB-C Hub", price: 1499),
Product(title: "Laptop Sleeve", price: 899)
]
override func viewDidLoad() {
super.viewDidLoad()
title = "Products"
view.backgroundColor = .systemBackground
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 12
layout.minimumLineSpacing = 12
layout.sectionInset = UIEdgeInsets(
top: 16,
left: 16,
bottom: 16,
right: 16
)
collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: layout
)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .systemBackground
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(
ProductCollectionViewCell.self,
forCellWithReuseIdentifier:
ProductCollectionViewCell.reuseIdentifier
)
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.topAnchor
),
collectionView.leadingAnchor.constraint(
equalTo: view.leadingAnchor
),
collectionView.trailingAnchor.constraint(
equalTo: view.trailingAnchor
),
collectionView.bottomAnchor.constraint(
equalTo: view.bottomAnchor
)
])
}
func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
products.count
}
func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier:
ProductCollectionViewCell.reuseIdentifier,
for: indexPath
) as! ProductCollectionViewCell
let product = products[indexPath.item]
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
let priceText = formatter.string(
from: product.price as NSDecimalNumber
) ?? product.price.description
cell.configure(title: product.title, price: priceText)
return cell
}
func collectionView(
_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath
) {
print("Selected: \(products[indexPath.item].title)")
}
}
This controller demonstrates registering and configuring collection view cells. The example uses a fixed grid size in the next section; for production interfaces, consider adapting the item width to the available space.
14. Calculating Collection View Cell Sizes
Collection view cells can use fixed sizes, estimated sizes, or sizes determined by layout rules. With UICollectionViewFlowLayout, the delegate can provide an item size based on the collection view's width.
Example: Responsive Two-Column Grid
extension ProductGridViewController:
UICollectionViewDelegateFlowLayout {
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let insets = layout.sectionInset
let spacing = layout.minimumInteritemSpacing
let availableWidth =
collectionView.bounds.width
- insets.left
- insets.right
- spacing
let itemWidth = floor(availableWidth / 2)
return CGSize(width: itemWidth, height: 190)
}
}
This example calculates two equal-width columns. For interfaces that support multiple device sizes, split views, or more complex arrangements, consider adjusting the column count based on available width or using a compositional layout.
15. Using Storyboards to Create Custom Cells
Interface Builder can be used to design a cell visually instead of creating every view in code.
Storyboard Workflow for a Table View
- Open the storyboard containing the table view.
- Select the table view and add a prototype cell.
- Set the cell style to Custom.
- Assign a reusable identifier such as
ContactCell.
- Set the custom class to your
UITableViewCell subclass.
- Add labels, image views, and buttons to the cell.
- Connect outlets to the custom cell class.
- Add constraints to define the cell layout.
- Configure the cell from the table view data source.
Storyboard Workflow for a Collection View
- Add a collection view to the view controller.
- Select the prototype collection view cell.
- Set its reusable identifier.
- Assign the custom
UICollectionViewCell subclass.
- Add the required interface elements.
- Connect outlets and create Auto Layout constraints.
- Configure the collection view layout.
- Provide the correct data source and delegate methods.
When using prototype cells in a storyboard, they are generally registered through the storyboard configuration. Avoid registering a programmatic cell class with the same identifier if doing so would replace the storyboard prototype cell registration.
16. Using XIB Files for Reusable Cells
A XIB file allows a custom cell to be designed separately from the view controller. This is useful when a cell is shared by multiple screens or when the interface is easier to manage visually.
Basic XIB Workflow
- Create a new Cocoa Touch class that inherits from
UITableViewCell or UICollectionViewCell.
- Create a corresponding XIB file.
- Set the root view's custom class to the cell subclass.
- Set the reuse identifier consistently.
- Add the required controls and connect outlets.
- Register the XIB with the appropriate table or collection view.
- Dequeue the cell using the registered identifier.
Example Registration for a Table View XIB
let nib = UINib(
nibName: "ContactTableViewCell",
bundle: nil
)
tableView.register(
nib,
forCellReuseIdentifier: ContactTableViewCell.reuseIdentifier
)
The XIB filename and the reuse identifier must match the values used by the project. For collection view XIBs, use register(_:forCellWithReuseIdentifier:) with the corresponding UINib.
17. Handling Button Actions Inside Custom Cells
Custom cells may contain buttons for actions such as favoriting a contact, adding a product to a cart, or marking a task as completed. A cell should communicate the user's action to the view controller or view model rather than independently managing the entire data source.
Using a Closure Callback
import UIKit
final class FavoriteContactCell: UITableViewCell {
static let reuseIdentifier = "FavoriteContactCell"
private let nameLabel = UILabel()
private let favoriteButton = UIButton(type: .system)
var onFavoriteTapped: (() -> Void)?
override init(
style: UITableViewCell.CellStyle,
reuseIdentifier: String?
) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
}
private func setupViews() {
nameLabel.translatesAutoresizingMaskIntoConstraints = false
favoriteButton.translatesAutoresizingMaskIntoConstraints = false
favoriteButton.setImage(
UIImage(systemName: "star"),
for: .normal
)
favoriteButton.addTarget(
self,
action: #selector(favoriteTapped),
for: .touchUpInside
)
contentView.addSubview(nameLabel)
contentView.addSubview(favoriteButton)
NSLayoutConstraint.activate([
nameLabel.leadingAnchor.constraint(
equalTo: contentView.layoutMarginsGuide.leadingAnchor
),
nameLabel.centerYAnchor.constraint(
equalTo: contentView.centerYAnchor
),
favoriteButton.trailingAnchor.constraint(
equalTo: contentView.layoutMarginsGuide.trailingAnchor
),
favoriteButton.centerYAnchor.constraint(
equalTo: contentView.centerYAnchor
),
favoriteButton.leadingAnchor.constraint(
greaterThanOrEqualTo: nameLabel.trailingAnchor,
constant: 12
),
favoriteButton.widthAnchor.constraint(equalToConstant: 44),
favoriteButton.heightAnchor.constraint(equalToConstant: 44),
contentView.heightAnchor.constraint(
greaterThanOrEqualToConstant: 60
)
])
}
func configure(name: String, isFavorite: Bool) {
nameLabel.text = name
let imageName = isFavorite ? "star.fill" : "star"
favoriteButton.setImage(
UIImage(systemName: imageName),
for: .normal
)
favoriteButton.accessibilityLabel =
isFavorite ? "Remove from favorites" : "Add to favorites"
}
@objc private func favoriteTapped() {
onFavoriteTapped?()
}
override func prepareForReuse() {
super.prepareForReuse()
onFavoriteTapped = nil
nameLabel.text = nil
}
}
The view controller assigns the closure when configuring the cell. Capture the controller weakly when necessary to avoid a retain cycle. If a list uses a diffable data source, prefer identifying the model by a stable identifier rather than relying only on an index path that may change after updates.
18. Managing Cell Selection and Highlight States
UIKit provides selection and highlight states to communicate interaction. Developers can customize how cells appear when tapped, selected, or disabled.
Common Interaction States
- Normal: The cell displays its default appearance.
- Highlighted: The cell responds visually while being pressed.
- Selected: The item is selected according to the app's behavior.
- Disabled: The item or its action is unavailable.
For table views, selection is commonly handled using tableView(_:didSelectRowAt:). For collection views, use collectionView(_:didSelectItemAt:). Avoid using visual selection as the only way to communicate an important state, such as whether a payment has completed or a task is overdue.
19. Dynamic Cell Heights and Self-Sizing Cells
Cell content may vary in length. A contact row might have one or two lines, while a message or note preview might require several lines. Self-sizing cells allow the layout system to determine the required height from the content and constraints.
Best Practices for Dynamic Heights
- Use Auto Layout constraints to connect the top and bottom of the cell content.
- Allow labels to wrap when necessary.
- Use suitable line limits where the design requires them.
- Set a reasonable estimated row height for table views.
- Ensure image views have meaningful size constraints.
- Test with long text and larger accessibility font sizes.
Table View Configuration
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 80
For collection views, self-sizing behavior depends on the selected layout and its configuration. Use estimated item sizes or appropriate compositional layout rules when item heights need to adapt to their content.
20. Using Modern Cell Registration and Diffable Data Sources
Modern UIKit offers APIs that can simplify cell configuration and data updates. Cell registration can keep the registration and configuration logic together, while diffable data sources manage snapshots of identifiable sections and items.
Why These APIs Are Useful
- Reduce manual bookkeeping for item positions.
- Make updates and animated changes easier to manage.
- Encourage stable identifiers for records.
- Reduce mistakes when items are inserted, removed, or reordered.
- Separate data identity from the current index path.
When working with older iOS deployment targets, the traditional data source and delegate pattern remains useful. Choose APIs according to the project's deployment target and complexity.
Example: UITableView Cell Registration
let registration =
UITableView.CellRegistration {
cell, indexPath, contact in
var content = cell.defaultContentConfiguration()
content.text = contact.name
content.secondaryText = contact.phone
content.image = UIImage(systemName: "person.circle")
cell.contentConfiguration = content
}
let cell = tableView.dequeueConfiguredReusableCell(
using: registration,
for: indexPath,
item: contacts[indexPath.row]
)
This approach uses UIKit's content configuration system for a compact cell. For highly customized layouts, a custom cell subclass or a SwiftUI-based cell configuration can still be appropriate.
21. Using SwiftUI Inside UIKit Cells
On supported iOS versions, UIHostingConfiguration allows a UIKit table or collection view cell to display SwiftUI content. This can be helpful when an existing UIKit application wants to reuse SwiftUI views without converting the entire screen to SwiftUI.
Example: SwiftUI Content in a Table View Cell
import SwiftUI
import UIKit
struct ContactRowContent: View {
let name: String
let phone: String
var body: some View {
HStack(spacing: 12) {
Image(systemName: "person.circle.fill")
.font(.system(size: 42))
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 4) {
Text(name)
.font(.headline)
Text(phone)
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 6)
}
}
// Inside tableView(_:cellForRowAt:):
cell.contentConfiguration = UIHostingConfiguration {
ContactRowContent(
name: contact.name,
phone: contact.phone
)
}
The hosting configuration manages the SwiftUI content inside the UIKit cell. Check the minimum iOS version supported by the project before using this API.
22. Improving Cell Performance
Because table and collection views may contain hundreds or thousands of items, cell performance is important. Poor cell configuration can cause slow scrolling, flickering images, excessive memory use, and delayed responses.
Performance Best Practices
- Always dequeue reusable cells instead of creating new cells for every request.
- Keep expensive work out of the cell configuration method.
- Avoid repeated database queries for every visible row when data can be prepared efficiently.
- Cache images and avoid decoding large images repeatedly on the main thread.
- Use stable model identifiers for asynchronous operations.
- Reset reused content and cancel obsolete asynchronous work where appropriate.
- Use self-sizing layouts carefully and test with realistic data.
- Use Instruments and Xcode debugging tools to identify actual performance bottlenecks.
Do not optimize based only on assumptions. Profile the application while scrolling a realistic dataset and focus on work that measurably affects frame rate, memory, or responsiveness.
23. Accessibility in Custom Cells
Custom cell designs should be usable by people who rely on VoiceOver, larger text, and other accessibility features. Customization should improve clarity without removing the system's expected interaction behavior.
- Give interactive buttons meaningful accessibility labels.
- Use Dynamic Type-compatible fonts.
- Maintain sufficient color contrast.
- Ensure touch targets are large enough for comfortable interaction.
- Do not rely on color alone to communicate status.
- Group related elements logically for VoiceOver.
- Test the layout using larger accessibility text sizes.
- Make sure important content remains visible when text wraps.
24. Common Errors and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Unable to dequeue a cell |
Incorrect reuse identifier or missing registration |
Verify the identifier and register the class, XIB, or storyboard prototype correctly. |
| Cell content is missing |
Configuration method not called or outlets not connected |
Check the cell configuration and Interface Builder connections. |
| Old data appears in reused cells |
Content was not reset or fully configured |
Assign all data-dependent values and reset reusable content. |
| Cell height is incorrect |
Incomplete Auto Layout constraints |
Connect content to the cell's top and bottom and check ambiguous constraints. |
| Collection items overlap |
Incorrect item size or spacing calculations |
Review layout insets, spacing, and available width. |
| Button triggers the wrong record |
Action depends on a stale index path |
Use a stable model identifier or update the callback when configuring the cell. |
| Scrolling is slow |
Expensive work or image loading during configuration |
Cache results and move suitable work off the main thread. |
| Image flickers or changes unexpectedly |
Asynchronous image result belongs to an older cell item |
Validate the current item identifier before applying the result. |
| Text is clipped |
Fixed height or insufficient constraints |
Support self-sizing and test larger text sizes. |
25. Practical Project: Contacts Table and Products Grid
Build a small UIKit application containing two screens: a contacts list using custom table view cells and a product gallery using custom collection view cells.
Screen 1: Contacts List
- Display a profile icon, name, and phone number.
- Support row selection.
- Add a favorite button.
- Allow the list to scroll smoothly.
- Handle long names and larger font sizes.
Screen 2: Products Grid
- Display product images or placeholder icons.
- Show product names and prices.
- Arrange products in a two-column grid.
- Support item selection.
- Adapt item widths to the available screen space.
Suggested Project Structure
CustomCellsApp/
├── Models/
│ ├── Contact.swift
│ └── Product.swift
├── Cells/
│ ├── ContactTableViewCell.swift
│ └── ProductCollectionViewCell.swift
├── ViewControllers/
│ ├── ContactsViewController.swift
│ └── ProductGridViewController.swift
└── Resources/
└── Assets.xcassets
Project Development Steps
- Create the Swift models.
- Build and test the custom table view cell.
- Register the cell and display contact records.
- Add selection handling and a favorite action.
- Create the custom collection view cell.
- Register the collection cell and display product records.
- Configure the collection layout and item sizing.
- Test cell reuse by scrolling repeatedly.
- Test long text, accessibility settings, and different device sizes.
- Refactor repeated logic into reusable methods where appropriate.
26. Hands-On Exercises
- Create a custom table view cell with a title, subtitle, and image.
- Register the cell and display at least ten sample records.
- Add a favorite button that updates the corresponding model.
- Implement row selection and display the selected record's details.
- Create a custom collection view cell with an image, title, and price.
- Display items in a two-column grid.
- Calculate item widths dynamically based on collection view width.
- Use a XIB file to create a reusable custom cell.
- Test cell reuse by scrolling through a larger data set.
- Fix any layout issues caused by longer text.
- Add accessibility labels to buttons inside the cells.
- Experiment with UIKit content configurations or SwiftUI hosting configurations on supported iOS versions.
27. Interview Questions and Answers
Q1. What is a UITableViewCell?
UITableViewCell is the UIKit class used to represent an individual row in a table view.
Q2. What is a UICollectionViewCell?
UICollectionViewCell represents an individual item in a collection view, which can display content in a grid, carousel, or custom layout.
Q3. Why are custom cells used?
Custom cells allow developers to design reusable layouts containing multiple labels, images, buttons, and other controls.
Q4. What is cell reuse?
Cell reuse allows offscreen cells to be configured for new items instead of creating a new view for every row or item.
Q5. What is a reuse identifier?
A reuse identifier identifies a registered cell type so UIKit can dequeue an appropriate reusable cell.
Q6. What is the purpose of prepareForReuse()?
It gives a cell an opportunity to reset transient reusable state before it is configured for another item. The main configuration method must still set all data-dependent content correctly.
Q7. What is the difference between a data source and a delegate?
The data source provides the content and cells. The delegate handles interactions and other behaviors, such as item selection and certain layout decisions.
Q8. What is Auto Layout?
Auto Layout is a constraint-based system that determines the size and position of views based on relationships between them.
Q9. How can a table view cell adjust its height automatically?
Use appropriate Auto Layout constraints, enable automatic row height, and provide an estimated row height.
Q10. How does a collection view differ from a table view?
A table view is designed primarily for rows in a list, while a collection view supports more flexible layouts such as grids, horizontal carousels, and custom arrangements.
Q11. How should button actions inside reusable cells be handled?
Use a closure, delegate, or another clear communication mechanism. Identify the relevant model reliably and avoid depending on stale index paths.
Q12. How can cell scrolling performance be improved?
Reuse cells, avoid expensive work during configuration, cache images, handle asynchronous operations correctly, and profile the interface with realistic data.
Q13. What is UIHostingConfiguration?
It is a UIKit API that allows SwiftUI content to be displayed inside table view and collection view cells on supported iOS versions.
Q14. When should a XIB be used for a custom cell?
A XIB is useful when a cell's interface should be designed visually in a separate file and reused across multiple screens.
28. Quick Revision Checklist
- Understand UITableView and UICollectionView.
- Know the difference between table and collection cells.
- Create custom cell subclasses.
- Build layouts with Auto Layout.
- Register and dequeue cells correctly.
- Configure cells from model data.
- Reset reusable content where needed.
- Handle selection and button actions safely.
- Understand self-sizing cells and collection layouts.
- Use accessibility labels and Dynamic Type.
- Optimize scrolling and asynchronous image loading.
- Test on different screen sizes and with realistic data.
29. Conclusion
Custom table view cells and collection view cells help developers build flexible, reusable, and visually consistent iOS interfaces. Table views are especially useful for structured lists, while collection views are ideal for grids, carousels, and more flexible item arrangements.
By learning custom cell subclasses, Auto Layout, registration, dequeuing, data configuration, cell reuse, and interaction handling, developers can build efficient interfaces that are easier to maintain. Performance, accessibility, and reliable model updates should remain part of the design from the beginning.
30. Additional Learning Resources